<?php

namespace Trawind\Basics\Traits;


use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Trawind\Basics\Enums\BusinessCode;
use Trawind\Basics\Enums\SystemConstant;
use Trawind\Basics\Traits\Transform\TransformView;
use Trawind\ConfigCenter\Helpers\ConfigHelper;
use Trawind\Extension\Models\Deploy\BusinessType;
use Trawind\Extension\Models\Deploy\BusinessTypeQueryer;
use Trawind\Helpers\HttpClientHelper;
use Trawind\Helpers\StrHelper;


trait RepositoryEnhance
{

    use TransformView;

    /**
     * pagination or list
     *
     * @param  bool      $skipPresenter
     * @param  int|null  $limit
     * @param  string[]  $columns
     * @return array|mixed|object
     */
    public function paginateOrAll(bool $skipPresenter = false, ?int $limit = null, $columns = ['*'])
    {
        return $this->viewOfDefault($skipPresenter, $limit, $columns);
    }


    /**
     * @param            $id
     * @param  bool      $skipPresenter
     * @param  string[]  $columns
     * @return array|mixed|null
     */
    public function skipFind($id, bool $skipPresenter = false, $columns = ['*'])
    {
        $data = $this->singleFillTransformData(parent::find($id, $columns));
        return $this->skipParserResult($data, $skipPresenter);
    }


    /**
     * @param  array  $attributes
     * @param  bool   $skipPresenter
     * @return array|mixed|null
     */
    public function skipCreate(array $attributes, bool $skipPresenter = false)
    {
        return $this->skipParserResult(parent::create($attributes), $skipPresenter);

//        $data = $this->singleFillTransformData(parent::create($attributes));
//        return $this->skipParserResult($data, $skipPresenter);
    }


    /**
     * @param  array  $attributes
     * @param         $id
     * @param  bool   $skipPresenter
     * @return array|mixed|null
     */
    public function skipUpdate(array $attributes, $id, bool $skipPresenter = false)
    {
        return $this->skipParserResult(parent::update($attributes, $id), $skipPresenter);

//        $data = $this->singleFillTransformData(parent::update($attributes, $id));
//        return $this->skipParserResult($data, $skipPresenter);
    }


    public function skipParserResult($data, bool $skipPresenter = false)
    {
        return $this->skipPresenter($skipPresenter)->parserResult($data);
    }


    /**
     * singleFillTransformData
     *
     * @param $data
     * @return mixed|null
     */
    public function singleFillTransformData($data)
    {
        $results = $this->fillTransformData([$data]);
        return array_shift($results);
    }


    /**
     * @param  array|object  $data
     * @return mixed
     */
    public function fillTransformData($data)
    {
        return $this->diyFillTransformData($this->fillTransformMap($data));
    }


    /**
     * Customize the conversion data function
     *
     * @param $data
     * @return mixed
     */
    protected function diyFillTransformData($data)
    {
        return $data;
    }


    /**
     * Whether it is a conversion field of include
     *
     * @param $field
     * @return bool
     */
    private static function isIncludeTransformField($field)
    {
        $includeArr = explode(',', request()->input('include'));

        if (Str::contains($field, '.')) {
            $prefix = Str::before($field, '.');
            return !in_array($prefix, $includeArr, true);
        }
        return false;
    }


    /**
     * Fill in the conversion data of the associated id
     *
     * @param $data
     * @return mixed
     */
    protected function fillTransformMap($data)
    {
        if (count($data) === 0) {
            return $data;
        }

        $result = $promises = [];
        $httpClient = HttpClientHelper::factory()->client();

        $isRemote = HttpClientHelper::isRemoteRequest();
        foreach (static::transformMap() as $remoteClass => $maps) {
            if ($isRemote && !in_array($remoteClass, static::needTransformRemote(), true)) {
                continue;
            }

            $remoteIds = $result[$remoteClass] = [];
            foreach ($maps as $map) {
                if (static::isIncludeTransformField($map)) {
                    continue;
                }

                //$ids = Str::contains($map, '*') ? head(Arr::pluck($data, $map)) : Arr::pluck($data, $map);
                if (Str::contains($map, '*')) {
                    $ids = [];
                    $getTem = Arr::pluck($data, $map);
                    foreach ($getTem as $val) {
                        $val = is_array($val) ? $val : [];
                        $ids = array_merge($ids, $val);
                    }
                } else {
                    $ids = Arr::pluck($data, $map);
                }
                is_null($ids) or $remoteIds = array_merge($remoteIds, $ids);
            }

//            $result[$remoteClass] = app($remoteClass)->getListById($remoteIds);

            if (Str::contains($remoteClass, 'Trawind\Remotes')) {
                $remote = app($remoteClass);
                $promises[$remoteClass] = $httpClient->getAsync($remote->baseRoute,
                    $remote->buildPromiseOptions($remoteIds));
            } else {
                $result[$remoteClass] = app($remoteClass)->getListById($remoteIds);
            }
        }

        // extend
        if (method_exists($this, 'transformMapExtend')) {
            $extendData = $this->transformMapExtendPromises($httpClient, $data);
            foreach ($extendData as $extend) {
                $result = $result + $extend['result'];
                $promises = $promises + $extend['promises'];
            }
        }

        $result = HttpClientHelper::resolve($promises) + $result;

        foreach (static::transformMap() as $remoteClass => $maps) {
            if ($isRemote && !in_array($remoteClass, static::needTransformRemote())) {
                continue;
            }

            foreach ($maps as $map) {
                if (static::isIncludeTransformField($map)) {
                    continue;
                }
                $fieldKey = substr($map, 0, -3);
                $map = StrHelper::uncamel($map);

                foreach ($data as &$item) {
                    $itemArr = is_object($item) ? $item->toArray() : $item;
                    if (Str::contains($map, '*')) {

                        foreach (data_get($itemArr, $map) as $i => $remoteId) {
                            $_fieldKey = Str::replaceFirst('*', $i, $fieldKey);
                            $transformValue = ['data' => $result[$remoteClass][$remoteId] ?? (object) []];

                            if (is_object($item)) {
                                static::objectSetValue($item, explode('.', $_fieldKey), $transformValue);
                            } else {
                                Arr::set($itemArr, $_fieldKey, $transformValue);
                            }
                        }
                    } else {
                        $index = Arr::get($itemArr, $map);
                        $transformValue = ['data' => $result[$remoteClass][$index] ?? (object) []];

                        if (is_object($item)) {
                            static::objectSetValue($item, explode('.', $fieldKey), $transformValue);
                        } else {
                            Arr::set($item, $fieldKey, $transformValue);
                        }
                    }
                }
            }
        }

        // extend set
        if (method_exists($this, 'fillTransformMapExtend')) {
            $data = $this->fillTransformMapExtend($data, $result);
        }

        return $data;
    }


    private static function objectSetValue(&$object, $keys, $value)
    {
        if (is_null($keys)) {
            return $object;
        }
        while (count($keys) > 1) {
            $key = array_shift($keys);

            if (!isset($object[$key]) || !is_object($object[$key])) {
                return $object;
            }

            $object = &$object[$key];
        }
        $object[array_shift($keys)] = $value;

        return $object;
    }


    /**
     * fill in personalized conversion data
     *
     * @return array
     * @example
     * return [
     *      UserRemoteEloquent::class => [
     *           'sku.data.created_id',
     *           'companies.*.company_id',
     *           'sku.data.updated_id',
     *           'sku.data.owner_id',
     *       ],
     * ];
     */
    public static function transformMap()
    {
        return [];
    }


    /**
     * does the remote request need to be converted
     *
     * @return array
     * @example
     * return [
     *      UserRemoteEloquent::class,
     *      RoleRemoteEloquent::class
     *       ],
     * ];
     */
    protected static function needTransformRemote()
    {
        return [];
    }


    /**
     * @param  array  $ids
     * @param  bool   $isColumnData
     * @return array
     */
    public function getListById(array $ids, bool $isColumnData = true)
    {
        if (empty($ids = array_unique($ids))) {
            return [];
        }

        $isSkipPresenter = $this->skipPresenter;

        $result = $this->skipParserResult($this->model->whereIn('id', $ids)->get());
        
        $data = $isColumnData ? array_column($result['data'] ?? [], null, 'id') : $result;

        $this->skipPresenter($isSkipPresenter);
        return $data;
    }

    /**
     * @param  array  $ids
     * @param  bool   $isColumnData
     * @return array
     */
    public function getListByField(array $ids, $field = 'id', bool $isColumnData = true)
    {
        if (empty($ids = array_unique($ids))) {
            return [];
        }

        $isSkipPresenter = $this->skipPresenter;

        $result = $this->skipParserResult($this->model->whereIn($field, $ids)->get());
        $data = $isColumnData ? array_column($result['data'] ?? [], null, $field) : $result;

        $this->skipPresenter($isSkipPresenter);
        return $data;
    }

    /**
     * @param  array  $ids
     * @param  int    $status_type_id
     * @return mixed
     * @throws \Prettus\Repository\Exceptions\RepositoryException
     */
    public function batchUpdateStatus(array $ids, int $status_type_id)
    {
        $result = $this->skipParserResult($this->model->whereIn('id', $ids)->get());

        foreach ($result['data'] as &$item) {
            $item['message'] = ___(BusinessCode::UPDATED_OK());
            if ($item['status_type_id'] == SystemConstant::INVALID) {
                $ids = array_merge(array_diff($ids, [$item['id']]));
                build_friendly_tips($item, ___('exception.in_invalid'), $this->businessType ?? null);
            }
        }

        unset($item);
        $this->makeModel()->whereIn('id', $ids)->update(compact('status_type_id'));
        $this->makeModel()->writeLog($ids);

        return $result;
    }


    public function batchDelete(array $ids)
    {
        $result = $this->skipParserResult($this->model->whereIn('id', $ids)->get());

        foreach ($result['data'] as &$item) {
            $item['message'] = ___(BusinessCode::DELETED_OK());
        }

        $this->makeModel()->whereIn('id', $ids)->delete();
        method_exists($this->model(), 'writeLog') and $this->makeModel()->writeLog($ids);
        return $result;
    }


    public function remoteSearchHandle($model)
    {
        $businessTypeId = $model->businessType;
        $queryers = BusinessTypeQueryer::with('localField')
            ->where('business_type_id', $businessTypeId)
            ->whereIn('relation_business_type_id', explode(',', request('remote')))
            ->get()
            ->toArray();

        $remoteParam = HttpClientHelper::getRemoteParam();
        foreach ($queryers as $queryer) {
            if (in_array($relation_business_type_id = $queryer['relation_business_type_id'], $remoteParam['remote'])) {
                $result = BusinessType::getEloquentById($relation_business_type_id)
                        ->getListBySearch([
                            'include' => $remoteParam['include'][$relation_business_type_id] ?? '',
                            'with' => $remoteParam['with'][$relation_business_type_id] ?? '',
                            'search' => $remoteParam['search'][$relation_business_type_id] ?? '',
                            'searchFields' => $remoteParam['searchFields'][$relation_business_type_id] ?? '',
                            'searchJoin' => 'and',
                            'orderBy' => $remoteParam['orderBy'][$relation_business_type_id] ?? '',
                            'sortedBy' => $remoteParam['sortedBy'][$relation_business_type_id] ?? '',
                            'pageSize' => 0,
                        ])['data'] ?? [];
                $ids = array_column($result, 'id');

                $search = array_filter(explode(';', request('search')));
                $search[] = $queryer['local_field']['name'].':'.join(',', $ids);

                $searchFields = array_filter(explode(';', request('searchFields')));
                $searchFields[] = $queryer['local_field']['name'].':in';
                request()['search'] = join(';', $search);
                request()['searchFields'] = join(';', $searchFields);
            }
        }
    }

}
